package com.graviton;

import java.util.Arrays;
import java.util.List;

public class Parser {

    private JSONTokens tokens;

    public Object parser(JSONTokens tokenList) throws JsonParserException {
        this.tokens = tokenList;
        return parser();
    }

    /**
     * object = {} | { members }
     * members = pair | pair , members
     * pair = string : value
     * array = [] | [ elements ]
     * elements = value  | value , elements
     * value = string | number | object | array | true | false | null
     * @return
     * @throws JsonParserException
     */
    private Object parser() throws JsonParserException {
        JSONToken jsonToken = this.tokens.nextToken();
        if(jsonToken == null){
            return new JSONObject();
        }
        if(jsonToken.getTokenType().equals(JSONTokenType.BEGIN_ARRAY)){
            return parserJSONArray();
        }
        if(jsonToken.getTokenType().equals(JSONTokenType.BEGIN_OBJECT)){
            return parserJSONObject();
        }
        throw new JsonParserException("first token is error");
    }

    /**
     * array = [] | [ elements ]
     * elements = value  | value , elements
     * value = string | number | object | array | true | false | null
     * @return
     * @throws JsonParserException
     */
    private JSONArray parserJSONArray() throws JsonParserException {
        JSONArray jsonArray = new JSONArray();
        List<JSONTokenType> expectToken = Arrays.asList(
                JSONTokenType.END_ARRAY, JSONTokenType.BEGIN_OBJECT,
                JSONTokenType.BEGIN_ARRAY,JSONTokenType.NULL,JSONTokenType.NUMBER,
                JSONTokenType.BOOLEAN,JSONTokenType.STRING);
        String prevkey = null;
        while (tokens.hasNextToken()){
            JSONToken token = tokens.nextToken();
            JSONTokenType tokenType = token.getTokenType();
            String tokenValue = token.getTokenValue();
            switch (tokenType) {
                case BEGIN_OBJECT:
                    checkExpectToken(tokenType, expectToken);
                    jsonArray.addJSONObj(parserJSONObject());
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case END_OBJECT:
                    checkExpectToken(tokenType, expectToken);
                    return jsonArray;
                case BEGIN_ARRAY:
                    checkExpectToken(tokenType, expectToken);
                    jsonArray.addJSONArr(parserJSONArray());
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case NULL:
                    checkExpectToken(tokenType, expectToken);
                    jsonArray.addJSONObj(null);
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case NUMBER:
                    checkExpectToken(tokenType, expectToken);
                    if (tokenValue.contains(".") || tokenValue.contains("e") || tokenValue.contains("E")) {
                        jsonArray.addJSONObj(Double.valueOf(tokenValue));
                    } else {
                        Long num = Long.valueOf(tokenValue);
                        if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) {
                            jsonArray.addJSONObj(num);
                        } else {
                            jsonArray.addJSONObj(num.intValue());
                        }
                    }
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case BOOLEAN:
                    checkExpectToken(tokenType, expectToken);
                    jsonArray.addJSONObj("true".equals(token.getTokenValue()) ? true : false);
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case STRING:
                    checkExpectToken(tokenType, expectToken);
                    JSONToken preToken = tokens.prevToken();
                    /*
                     * 在 JSON 中，字符串既可以作为键，也可作为值。
                     * 作为键时，只期待下一个 Token 类型为 SEP_COLON。
                     * 作为值时，期待下一个 Token 类型为 SEP_COMMA 或 END_OBJECT
                     */
                    // :
                    if (JSONTokenType.SEP_COLON.equals(preToken.getTokenType())) {
                        jsonArray.addJSONObj(tokenValue);
                        expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    } else {
                        prevkey = tokenValue;
                        expectToken = Arrays.asList(JSONTokenType.SEP_COLON);
                    }
                    break;
                case SEP_COLON: // :
                    checkExpectToken(tokenType, expectToken);
                    expectToken = Arrays.asList(JSONTokenType.BEGIN_OBJECT,JSONTokenType.BEGIN_ARRAY,
                            JSONTokenType.NULL,JSONTokenType.NUMBER,JSONTokenType.STRING,
                            JSONTokenType.BOOLEAN);
                    break;
                case SEP_COMMA: // ,
                    checkExpectToken(tokenType, expectToken);
                    expectToken = Arrays.asList(JSONTokenType.STRING);
                    break;
                case END_DOCUMENT:
                    checkExpectToken(tokenType, expectToken);
                    return jsonArray;
                default:
                    throw new JsonParserException("Unexpected Token.");
            }
        }

        throw new JsonParserException("Parse error, invalid Token.");
    }

    /**
     * BEGIN_OBJECT（{）
     * END_OBJECT（}）
     * BEGIN_ARRAY（[）
     * END_ARRAY（]）
     * NULL（null）
     * NUMBER（数字）
     * STRING（字符串）
     * BOOLEAN（true/false）
     * SEP_COLON（:）
     * SEP_COMMA（,）
     * @return
     * @throws JsonParserException
     */
    private JSONObject parserJSONObject() throws JsonParserException {
        JSONObject jsonObject = new JSONObject();
        List<JSONTokenType> expectToken = Arrays.asList(JSONTokenType.STRING,JSONTokenType.END_OBJECT);
        String key = null;
        while (tokens.hasNextToken()){
            JSONToken token = tokens.nextToken();
            JSONTokenType tokenType = token.getTokenType();
            String tokenValue = token.getTokenValue();
            switch (tokenType) {
                case BEGIN_OBJECT:
                    checkExpectToken(tokenType, expectToken);
                    jsonObject.put(key, parserJSONObject());
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case END_OBJECT:
                    checkExpectToken(tokenType, expectToken);
                    return jsonObject;
                case BEGIN_ARRAY:
                    checkExpectToken(tokenType, expectToken);
                    jsonObject.put(key, parserJSONArray());
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case NULL:
                    checkExpectToken(tokenType, expectToken);
                    jsonObject.put(key, null);
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case NUMBER:
                    checkExpectToken(tokenType, expectToken);
                    if (tokenValue.contains(".") || tokenValue.contains("e") || tokenValue.contains("E")) {
                        jsonObject.put(key, Double.valueOf(tokenValue));
                    } else {
                        Long num = Long.valueOf(tokenValue);
                        if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) {
                            jsonObject.put(key, num);
                        } else {
                            jsonObject.put(key, num.intValue());
                        }
                    }
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case BOOLEAN:
                    checkExpectToken(tokenType, expectToken);
                    jsonObject.put(key, "true".equals(token.getTokenValue()) ? true : false);
                    expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    break;
                case STRING:
                    checkExpectToken(tokenType, expectToken);
                    JSONToken preToken = tokens.prevToken();
                    /*
                     * 在 JSON 中，字符串既可以作为键，也可作为值。
                     * 作为键时，只期待下一个 Token 类型为 SEP_COLON。
                     * 作为值时，期待下一个 Token 类型为 SEP_COMMA 或 END_OBJECT
                     */
                    // :
                    if (JSONTokenType.SEP_COLON.equals(preToken.getTokenType())) {
                        jsonObject.put(key, tokenValue);
                        expectToken = Arrays.asList(JSONTokenType.SEP_COMMA,JSONTokenType.END_OBJECT);
                    } else {
                        key = tokenValue;
                        expectToken = Arrays.asList(JSONTokenType.SEP_COLON);
                    }
                    break;
                case SEP_COLON: // :
                    checkExpectToken(tokenType, expectToken);
                    expectToken = Arrays.asList(JSONTokenType.BEGIN_OBJECT,JSONTokenType.BEGIN_ARRAY,
                            JSONTokenType.NULL,JSONTokenType.NUMBER,JSONTokenType.STRING,
                            JSONTokenType.BOOLEAN);
                    break;
                case SEP_COMMA: // ,
                    checkExpectToken(tokenType, expectToken);
                    expectToken = Arrays.asList(JSONTokenType.STRING);
                    break;
                case END_DOCUMENT:
                    checkExpectToken(tokenType, expectToken);
                    return jsonObject;
                default:
                    throw new JsonParserException("Unexpected Token.");
            }
        }

        throw new JsonParserException("Parse error, invalid Token.");
    }

    private void checkExpectToken(JSONTokenType tokenType, List<JSONTokenType> expectToken) throws JsonParserException {
        if(!expectToken.contains(tokenType)){
            throw new JsonParserException("Parse error, invalid Token excepted.");
        }
    }
}
